package cn.outsourcing.supermarket.common.utils;

import cn.outsourcing.supermarket.common.constants.WechatConstants;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * 数据处理工具类
 *
 * @author gzkemays
 * @since 2021/10/21 12:30
 */
public class DataUtils {
  private static final int WX_DECRYPT_SESSION_KEY_LEN = 24;
  private static final int MILLIS_LENGTH = 13;
  /** 积分换算为支付金额比率 */
  private static final BigDecimal INTEGRAL_CONVERSION_PIES = BigDecimal.TEN;

  public static Boolean isNumberByStr(String str) {
    return str.matches("[0-9]+");
  }

  /**
   * 数据库的 XL 转换为 X
   *
   * @param level 等级如：1L 2L
   * @return 1 2
   */
  public static Integer getLevelByLStr(String level) {
    return Integer.parseInt(level.toLowerCase().replace("l", ""));
  }
  /**
   * 根据指定长度生成字母和数字的随机数
   * <li>0~9的ASCII为48~57
   * <li>A~Z的ASCII为65~90
   * <li>a~z的ASCII为97~122
   *
   * @param length 生成长度
   * @return
   */
  public static String createRandomStr(int length) {
    StringBuilder sb = new StringBuilder();
    Random rand = new Random(); // 随机用以下三个随机生成器
    Random randdata = new Random();
    int data = 0;
    for (int i = 0; i < length; i++) {
      int index = rand.nextInt(3);
      // 目的是随机选择生成数字，大小写字母
      switch (index) {
        case 0:
          data = randdata.nextInt(10); // 仅仅会生成0~9
          sb.append(data);
          break;
        case 1:
          data = randdata.nextInt(26) + 65; // 保证只会产生65~90之间的整数
          sb.append((char) data);
          break;
        case 2:
          data = randdata.nextInt(26) + 97; // 保证只会产生97~122之间的整数
          sb.append((char) data);
          break;
      }
    }
    String result = sb.toString();
    return result;
  }

  /**
   * 根据积分获取金额
   *
   * @param integral 积分
   * @return 积分金额
   */
  public static BigDecimal getPriceByIntegral(BigDecimal integral) {
    BigDecimal divide = integral.divide(INTEGRAL_CONVERSION_PIES, 2, RoundingMode.HALF_DOWN);
    return divide.compareTo(BigDecimal.ZERO) > 0 ? divide : BigDecimal.ZERO;
  }

  /**
   * 通过实际支付以及原价计算出使用的积分
   *
   * @param price 原价
   * @param actualPrice 实际价格
   * @return 应当使用的积分
   */
  public static BigDecimal getUsedIntegral(BigDecimal price, BigDecimal actualPrice) {
    return price.subtract(actualPrice).multiply(INTEGRAL_CONVERSION_PIES);
  }

  /**
   * 获取最大使用积分
   *
   * <p>用户使用积分时都是按照最大积分进行使用
   *
   * @param price 商品价格
   * @param integral 用户积分
   * @return 最大可用积分
   */
  public static BigDecimal getMaxIntegral(BigDecimal price, BigDecimal integral) {
    BigDecimal maxIntegral =
        Optional.ofNullable(integral)
            .map(
                e -> {
                  BigDecimal divide = e.divide(INTEGRAL_CONVERSION_PIES, 2, RoundingMode.HALF_UP);
                  // 判断积分兑换成支付金额时是否大于支付金额？
                  if (divide.compareTo(price) > -1) {
                    // 返回最大可以使用的积分
                    return price.multiply(INTEGRAL_CONVERSION_PIES);
                  }
                  return divide.multiply(INTEGRAL_CONVERSION_PIES);
                })
            .orElse(BigDecimal.ZERO);
    return maxIntegral;
  }

  /**
   * 将 json 写入 response 响应
   *
   * @param request 请求来源
   * @param response 响应返回
   * @param data 写入数据
   */
  public static void writeJson(
      HttpServletRequest request, HttpServletResponse response, Object data) {
    // 这里很重要，否则页面获取不到正常的JSON数据集
    response.setContentType("application/json;charset=UTF-8");
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Method", "*");
    // 输出JSON
    PrintWriter out = null;
    try {
      out = response.getWriter();
      out.write(new ObjectMapper().writeValueAsString(data));
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (Objects.nonNull(out)) {
        out.flush();
        out.close();
      }
    }
  }

  /**
   * 盐值转化 MD5
   *
   * @param str 源文本
   * @param salt 盐值
   * @return 转化结果
   */
  public static String saltMd5Str(String str, String salt) {
    return DigestUtils.md5DigestAsHex((str + salt).getBytes());
  }
  /**
   * 返回当前时间戳为秒的 int 数据
   *
   * @return 当前时间戳 -- 精准至秒
   */
  public static Integer getSeconds() {
    long time = System.currentTimeMillis();
    return Integer.parseInt(String.valueOf(time / 1000));
  }

  /**
   * 获取今天日期
   *
   * @return 日期
   */
  public static String getTodayDate() {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    long time = System.currentTimeMillis();
    return sdf.format(time);
  }

  /**
   * 获取时间戳的日期
   *
   * @param time 时间戳
   * @return 格式化日期
   */
  public static String getTimestampData(long time) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(toMillis(time));
  }

  public static String getTimestampAllData(long time) {
    if (Objects.nonNull(time) && time != 0L) {
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:SS");
      return sdf.format(toMillis(time));
    } else {
      return "0";
    }
  }

  /**
   * 获取 day 天后的时间戳
   *
   * @param day 天数
   * @return day 天后的时间戳
   */
  public static long getDayBeforeTimeStamp(int day) {
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DATE, +day);
    Date beforeDay = calendar.getTime();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.parse(sdf.format(beforeDay), new ParsePosition(0)).getTime() / 1000;
  }

  /**
   * 获取day天前的 0 点时间戳
   *
   * @param day 天
   * @return
   */
  public static long getBeforeTimestampZero(int day) {
    Calendar calendar = Calendar.getInstance();
    int date = day + 2;
    calendar.set(Calendar.DATE, date);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    return calendar.getTimeInMillis() / 1000;
  }
  /**
   * 获取当天的 0 点时间戳
   *
   * @return
   */
  public static long getTodayTimestampZero() {
    return getBeforeTimestampZero(0);
  }

  /**
   * 获取几分钟后的时间戳
   *
   * @param minute 分钟
   * @return 分钟前
   */
  public static long getMinuteBeforeTimeStamp(int minute) {
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.MINUTE, +minute);
    Date beforeDay = calendar.getTime();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.parse(sdf.format(beforeDay), new ParsePosition(0)).getTime() / 1000;
  }

  /**
   * 根据长度判断是否要变更为毫秒时间戳
   *
   * @param time 时间戳
   * @return 正确时间
   */
  public static long toMillis(long time) {
    if (String.valueOf(time).length() < MILLIS_LENGTH) {
      time = time * 1000L;
    }
    return time;
  }
  /**
   * 根据长度判断是否要变更为秒时间戳
   *
   * @param time 时间戳
   * @return 正确时间
   */
  public static Integer toSeconds(long time) {
    if (String.valueOf(time).length() >= MILLIS_LENGTH) {
      time = time / 1000L;
    }
    return (int) time;
  }
  /**
   * 判断时间戳是否是今天
   *
   * @param time 时间戳
   * @return 判断结果
   */
  public static boolean timestampIsToday(long time) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return getTodayDate().equals(sdf.format(toMillis(time)));
  }

  /**
   * 对象转化为 Map 对象
   *
   * @param object 对象实体类
   * @return Map 结果
   */
  public static Map<String, Object> entityParseMap(Object object) {
    Class<?> clazz = object.getClass();
    Field[] declaredFields = clazz.getDeclaredFields();
    Map<String, Object> map = new HashMap<>(declaredFields.length);
    for (Field field : declaredFields) {
      field.setAccessible(true);
      String key = field.getName();
      try {
        Object value = field.get(object);
        map.put(key, value);
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }
    return map;
  }
  /**
   * 对象转化为 Map 对象
   *
   * @param object 对象实体类
   * @return Map 结果
   */
  public static Map<String, String> entityParseMapWithStr(Object object) {
    Class<?> clazz = object.getClass();
    Field[] declaredFields = clazz.getDeclaredFields();
    Map<String, String> map = new HashMap<>(declaredFields.length);
    for (Field field : declaredFields) {
      field.setAccessible(true);
      String key = field.getName();
      try {
        Object value = field.get(object);
        map.put(key, String.valueOf(value));
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }
    return map;
  }

  /**
   * 对称解密
   * <li>对称解密使用的算法为 AES-128-CBC，数据采用PKCS#7填充
   * <li>对称解密的目标密文为 Base64_Decode(encryptedData)
   * <li>对称解密秘钥 aesKey = Base64_Decode(session_key), aesKey 是16字节。
   * <li>对称解密算法初始向量 为Base64_Decode(iv)，其中iv由数据接口返回
   *
   * @param appId 作用于校验
   * @param sessionKey 请求 j2Session 时获取的 sessionKey
   * @param encryptedData 小程序获取用户信息时的数据
   * @param iv iv 向量
   * @return 解密明文
   */
  public static String decryptWxEncrypted(
      String appId, String sessionKey, String encryptedData, String iv) {
    if (StringUtils.length(sessionKey) != WX_DECRYPT_SESSION_KEY_LEN) {
      return WechatConstants.ILLEGAL_AES_KEY;
    }
    if (StringUtils.length(iv) != WX_DECRYPT_SESSION_KEY_LEN) {
      return WechatConstants.ILLEGAL_IV;
    }
    try {
      // 对称解密秘钥 aesKey = Base64_Decode(session_key), aesKey 是16字节。
      byte[] aesKey = Base64.decodeBase64(sessionKey);
      // 对称解密算法初始向量 为Base64_Decode(iv)，其中iv由数据接口返回。
      byte[] aesIv = Base64.decodeBase64(iv);
      // 对称解密的目标密文为 Base64_Decode(encryptedData)
      byte[] aesCipher = Base64.decodeBase64(encryptedData);
      // 进行 Aes 对称解密
      byte[] resultByte = AesUtils.decrypt(aesCipher, aesKey, aesIv);
      if (null != resultByte && resultByte.length > 0) {
        String userInfo = new String(resultByte, StandardCharsets.UTF_8);
        JSONObject jsons = JSON.parseObject(userInfo);
        // 获取 ID 并判断校验是否相等
        String id = jsons.getJSONObject("watermark").getString("appid");
        if (!StringUtils.equals(id, appId)) {
          return WechatConstants.ILLEGAL_BUFFER;
        }
        return userInfo;
      } else {
        return WechatConstants.NO_DATA;
      }
    } catch (InvalidAlgorithmParameterException e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 根据 key 来过滤重复数据
   *
   * <p>配合 {@link java.util.stream.Stream#filter(Predicate)} 的过滤特性，返回 {@link Boolean#TRUE} 的结果。因此做
   * {@link ConcurrentHashMap#putIfAbsent(Object, Object)} 来判断 tempSaveMap 是否有相同的键值。如果有，tempSaveMap
   * 是不会存储当前对象的并且返回 {@code true}，如果没有对应的键值则返回 {@code null}。
   *
   * @param keyExtractor lambda 函数式实现接口
   * @param <T> 接受任意数据类型
   * @return {@link Boolean#TRUE} or {@link Boolean#FALSE} 搭配 filter 实现过滤
   */
  public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Map<Object, Boolean> tempSaveMap = new ConcurrentHashMap<>();
    return obj -> Objects.isNull(tempSaveMap.putIfAbsent(keyExtractor.apply(obj), Boolean.TRUE));
  }
}
